import * as THREE from "../../../modules/three/three.module.js";
import PluginBase from "./PluginBase.js";
import {d2r, MouseButton} from "../lib/CommonConsts.js";
import RoutePointVO from "../model/RoutePointVO.js";
import RoutePointIndicator from "../lib/gadget/RoutePointIndicator.js";
import RouteVO from "../model/RouteVO.js";
import {eventDetached, eventDraggingChanged, eventRotating} from "../lib/gadget/TransformRotateControl.js";
import {debounce} from "../lib/util/ToolMethods.js";
import UndoType from "../scene/undo/UndoType.js";
import World3dEvent from "../scene/event/World3dEvent.js";
import SimpleMarkEntity from "../lib/gadget/SimpleMarkEntity.js";

export const ConstraintEnum = {
  none: 'none',
  horizontal: 'horizontal',
  vertical: 'vertical',
  custom: 'custom',
}

const unitZ = new THREE.Vector3(0, 0, 1)
const unitY = new THREE.Vector3(0, 1, 0)
// 吸附到平面的阈值
const distanceAdhere = 0.6
const _raycaster = new THREE.Raycaster()
const tmp = new THREE.Vector3()
const mid = new THREE.Vector3()
const midDir = new THREE.Vector3()

const planeGeo = new THREE.PlaneGeometry(50, 50)
const mtlAdhere = new THREE.MeshBasicMaterial({transparent: true, opacity: 0.7, color: '#fffb11', side: THREE.DoubleSide})
const mtlConstraint = mtlAdhere.clone()
mtlConstraint.color.setStyle('#ff6000')
const planeMeshConstraint = new THREE.Mesh(planeGeo, mtlConstraint)
const routePointBak = new RoutePointVO()

let refTimeoutAdhere, refTimeoutConstraint
function showPlaneMeshShortly(scene, planeMesh, position, normal) {
  planeMesh.position.copy(position)
  planeMesh.quaternion.setFromUnitVectors(unitZ, normal)
  scene.add(planeMesh)
  // if (refTimeoutAdhere) {
  //   clearTimeout(refTimeoutAdhere)
  // } else if (refTimeoutConstraint) {
  //   clearTimeout(refTimeoutConstraint)
  // }
  // refTimeoutAdhere = refTimeoutConstraint = undefined
  // const refTimeout = setTimeout(() => planeMesh.removeFromParent(), 3000);
  // if (planeMesh === planeMeshAdhere) {
  //   refTimeoutAdhere = refTimeout
  // } else {
  //   refTimeoutConstraint = refTimeout
  // }
}

function clearFastArr() {
  console.log('clearFastArr, ', this.fastArr.length)
  this.fastArr.length = 0
}

class AdherePoint extends RoutePointVO {
  byPlanes = []
}

export default class RoutePointPlugin extends PluginBase {
  /**
   * @type {World3d}
   */
  world3d
  /**
   * @type {RoutePointIndicator|null}
   */
  _movingIndicator
  /**
   * @type {RoutePointIndicator|null}
   */
  _selectedIndicator
  constraint = ConstraintEnum.none
  /**
   * 可以被吸附的平面网格
   * @type {THREE.Mesh[]}
   */
  adherePlaneMeshes = []
  /**
   * 可以被吸附的虚拟航点
   * @type {AdherePoint[]}
   */
  adhereRoutePoints = []
  fastIndex = -1
  fastArr = []
  /**
   * @type {SimpleMarkEntity}
   */
  _distanceLabel

  constructor(world3d) {
    super()
    this.world3d = world3d
    this._distanceLabel = new SimpleMarkEntity('').setEye(world3d.camera)
    this._distanceLabel.visible = false;
    this._distanceLabel.renderOrder = 12;
    world3d.scene.add(this._distanceLabel);
    const store = world3d.store
    const tControls = this.world3d.transformRotateControl
    tControls.addEventListener(eventDetached.type, () => {
      const pArr = this.adherePlaneMeshes.concat([planeMeshConstraint])
      if (pArr.includes(tControls.object) || tControls.object instanceof RoutePointIndicator) {
        tControls.setTranslateVisible(true)
      }
    })
    tControls.addEventListener(eventRotating.type, () => {
      const pArr = this.adherePlaneMeshes.concat([planeMeshConstraint])
      if (pArr.includes(tControls.object)) {
        const pl = tControls.object.userData.plane
        pl.normal.copy(unitZ).applyQuaternion(tControls.object.quaternion)
        pl.setFromNormalAndCoplanarPoint(pl.normal, tControls.object.position)
      } else if (tControls.object instanceof RoutePointIndicator) {
        tControls.object.updateVO()
        tControls.object.syncToVO()
        tControls.object.updateLine4Drone()
        if (!store.currentRoute) {
          return
        }
        const arr = store.currentRoute.pointMeshes
        const index = arr.indexOf(tControls.object)
        if (index < 0 || index >= arr.length - 1) {
          return;
        }
        const indicatorNext = arr[index + 1]
        indicatorNext.updateLine4Drone(tControls.object.vo)
      }
    })
    tControls.addEventListener(eventDraggingChanged.type, () => {
      if (!eventDraggingChanged.rotated) {
        return;
      }
      if (!this.adherePlaneMeshes.includes(tControls.object)) {
        return;
      }
      this._refreshAdherePoints(tControls.object)
    })
  }

  // 新增航线
  createNewRoute() {
    const route = this.world3d.store.currentRoute
    const list = this.world3d.store.routeList
    if (route && route.pointMeshes.length && !list.includes(route)) {
      list.push(route)
    }
    this.world3d.store.currentRoute = new RouteVO()
  }

  // 更新单条航线的离墙距离
  updateRouteHorizontalOffset(offset) {
    const store = this.world3d.store
    const pickingRoute = store.pickingRoute
    if (!pickingRoute) {
      return
    }
    pickingRoute.pointMeshes.forEach(it => {
      // it.vo.offset = offset
      it.vo.updateOffsetSize(offset)
      it._syncDroneToVO()
    })
    pickingRoute.pointMeshes.forEach((it, index) => {
      if (index > 0) {
        const prev = pickingRoute.pointMeshes[index - 1]
        it.updateLine4Drone(prev.vo)
      }
    })
  }
  updateRouteOffsetType(offsetType) {
    const store = this.world3d.store
    const pickingRoute = store.pickingRoute
    if (!pickingRoute) {
      return
    }
    pickingRoute.pointMeshes.forEach(it => {
      it.vo.offsetType = offsetType
      it._syncDroneToVO()
    })
    pickingRoute.pointMeshes.forEach((it, index) => {
      if (index > 0) {
        const prev = pickingRoute.pointMeshes[index - 1]
        it.updateLine4Drone(prev.vo)
      }
    })
  }
  /**
   * 计算两个平面相交形成的直线，该直线再和模型相撞，撞出一个坐标
   * @param {THREE.Mesh} planeMeshA
   * @param {THREE.Mesh} planeMeshB
   * @return {null|AdherePoint}
   * @private
   */
  _computeAdhereRoutePoint(planeMeshA, planeMeshB) {
    const planeA = planeMeshA.userData.plane
    const planeB = planeMeshB.userData.plane
    if (Math.abs(planeA.normal.dot(planeB.normal)) > 0.98) {
      // 两个平面几乎平行
      return null
    }
    const ray = new THREE.Ray(planeMeshA.position.clone())
    const intersectsB = []
    const pointingGround = Math.abs(planeA.normal.dot(unitZ)) > 0.9
    if (pointingGround) {
      ray.direction.y = -planeA.normal.z
      ray.direction.z = planeA.normal.y
      ray.direction.x = planeA.normal.x
    } else {
      ray.direction.x = -planeA.normal.y
      ray.direction.y = planeA.normal.x
      ray.direction.z = planeA.normal.z
    }
    // dist suppose be 0
    const dist = planeA.distanceToPoint(planeMeshA.position.clone().add(ray.direction))
    // console.log('_computeAdhereRoutePoint, ', dist)
    const quat = new THREE.Quaternion().setFromAxisAngle(planeA.normal, 70 * d2r)
    while(intersectsB.length < 2) {
      const inter = ray.intersectPlane(planeB, new THREE.Vector3())
      if (inter) {
        intersectsB.push(inter)
      }
      ray.direction.applyQuaternion(quat)
    }
    const [a, b] = intersectsB
    ray.origin.copy(b)
    ray.direction.copy(a).sub(b).normalize()
    const eye = this.world3d.camera
    const eyeFacing = new THREE.Vector3().setFromMatrixColumn(eye.matrixWorld, 2).negate()
    const eyePlane = new THREE.Plane().setFromNormalAndCoplanarPoint(eyeFacing, eye.position)
    let pos = ray.intersectPlane(eyePlane, new THREE.Vector3())
    if (!pos) {
      ray.direction.negate()
      pos = ray.intersectPlane(eyePlane, new THREE.Vector3())
    }
    if (!pos) {
      // ray 和 eyePlane 平行
      return null
    } else {
      ray.origin.copy(pos)
    }
    if (ray.direction.dot(eyeFacing) < 0) {
      ray.direction.negate()
    }
    _raycaster.near = eye.near
    _raycaster.far = eye.far
    _raycaster.ray.copy(ray)
    const intersects = this._getIntersects(_raycaster)
    if (intersects.length) {
      const vo = new AdherePoint()
      vo.normal.copy(ray.direction).negate()
      vo.position.copy(intersects[0].point)
      return vo
    }
    return null
  }
  /**
   * @param {RoutePointVO} voStartOrEnd
   * @param {THREE.Vector3} direction
   * @return {RoutePointVO}
   * @private
   */
  _findTileIntersects(voStartOrEnd, direction) {
    const camera = this.world3d.camera
    _raycaster.near = camera.near
    _raycaster.far = camera.far
    const pos = voStartOrEnd.position
    const dist = 0.25
    const v0 = new THREE.Vector3().copy(direction).multiplyScalar(dist).add(pos)
    const v3 = new THREE.Vector3().copy(direction).negate().multiplyScalar(dist).add(pos)
    const dot = Math.abs(direction.dot(unitZ))
    let axis
    if (dot < 0.9) {
      axis = unitZ.cross(direction)
    } else {
      axis = unitY.cross(direction)
    }
    const quat = new THREE.Quaternion().setFromAxisAngle(axis, Math.PI * 0.5)
    const v1 = new THREE.Vector3().copy(direction).applyQuaternion(quat)
    const v2 = new THREE.Vector3().copy(v1).negate()
    v1.multiplyScalar(dist).add(pos)
    v2.multiplyScalar(dist).add(pos)
    const arr = [v0, v1, v2, v3]
    _raycaster.ray.direction.copy(voStartOrEnd.normal).negate()
    for (let i = 0; i < arr.length; i++) {
      _raycaster.ray.origin.copy(voStartOrEnd.normal).multiplyScalar(50).add(arr[i])
      const tileIntersects = this._getIntersects(_raycaster)
      if (tileIntersects.length) {
        const pointVO = new RoutePointVO()
        pointVO.position.copy(tileIntersects[0].point)
        pointVO.normal.copy(tileIntersects[0].face.normal)
        return pointVO
      }
    }
    return voStartOrEnd.cloneRoutePoint()
  }

  /**
   * 为已经存在的航线添加航点
   * @param {boolean} insertBefore, true在航线首(开始)方向加航点，false在航线尾（结束）方向加航点
   */
  insertRoutePoint(insertBefore) {
    const world3d = this.world3d
    const scene = this.world3d.scene
    const store = this.world3d.store
    const routePoint = this._selectedIndicator
    if (!routePoint) {
      return
    }
    const index = store.currentRoute.pointMeshes.indexOf(routePoint)
    const lst = store.currentRoute.pointMeshes
    let pointPrev, pointNext
    if (insertBefore) {
      pointNext = lst[index]
      if (index > 0) {
        pointPrev = lst[index - 1]
      }
    } else {
      pointPrev = lst[index]
      if (index < lst.length - 1) {
        pointNext = lst[index + 1]
      }
    }

    if (pointNext && pointPrev) {
      const camera = world3d.camera
      _raycaster.near = camera.near
      _raycaster.far = camera.far
      mid.copy(pointPrev.vo.position).lerp(pointNext.vo.position, 0.5)
      midDir.copy(pointPrev.vo.normal).add(pointNext.vo.normal).normalize()
      _raycaster.ray.origin.copy(midDir).multiplyScalar(3).add(mid)
      _raycaster.ray.direction.copy(midDir).negate()
      const tileIntersects = this._getIntersects(_raycaster)
      if (tileIntersects.length) {
        const midPointVO = new RoutePointVO()
        midPointVO.position.copy(tileIntersects[0].point)
        midPointVO.normal.copy(tileIntersects[0].face.normal)
        const midIndicator = new RoutePointIndicator(midPointVO, scene)
        midIndicator.inheritRouteProperties(store.currentRoute)
        world3d.scene.add(midIndicator)
        store.routePoints.push(midIndicator)
        if (insertBefore) {
          lst.splice(index, 0, midIndicator)
        } else {
          lst.splice(index + 1, 0, midIndicator)
        }
        midIndicator.createLine(world3d.scene, pointPrev.vo)
        pointNext.updateLine(midPointVO)
      }
    } else {
      // 在航线的首或尾添加航点
      let direction = new THREE.Vector3()
      let startOrEnd
      let latest1
      if (lst.length > 1) {
        latest1 = lst[lst.length - 1].vo
        if (pointPrev) {
          const latest2 = lst[lst.length - 2].vo
          direction.copy(latest1.position).sub(latest2.position).normalize()
          startOrEnd = latest1
        } else {
          direction.copy(lst[0].vo.position).sub(lst[1].vo.position).normalize()
          startOrEnd = lst[0].vo
        }
      } else {
        startOrEnd = lst[0].vo
        const dir = world3d.camera.position.clone().sub(startOrEnd.position)
        const dot = Math.abs(dir.dot(unitZ))
        if (dot > 0.95) {
          direction.set(0, 1, 0)
        } else {
          direction.set(0, 0, 1)
        }
      }
      const pointVO = this._findTileIntersects(startOrEnd, direction)
      const indicator = new RoutePointIndicator(pointVO, scene)
      indicator.inheritRouteProperties(store.currentRoute)
      world3d.scene.add(indicator)
      store.routePoints.push(indicator)
      if (lst.length === 1) {
        const first = lst[0]
        if (insertBefore) {
          first.createLine(world3d.scene, pointVO)
          lst.unshift(indicator)
        } else {
          indicator.createLine(world3d.scene, first.vo)
          lst.push(indicator)
        }
      } else {
        if (pointPrev) {
          lst.push(indicator)
          indicator.createLine(world3d.scene, latest1)
        } else {
          const first = lst[0]
          first.createLine(world3d.scene, pointVO)
          lst.unshift(indicator)
        }
      }
    }
    //
  }

  _updateIndicatorLines(pickingIndicator) {
    const store = this.world3d.store
    const curRoute = store.currentRoute
    const index = curRoute.pointMeshes.indexOf(pickingIndicator)
    const lst = store.currentRoute.pointMeshes
    let pointPrev, pointNext
    if (index > 0) {
      pointPrev = lst[index - 1]
    }
    if (index < lst.length - 1) {
      pointNext = lst[index + 1]
    }
    if (pointNext) {
      pointNext.updateLine(pickingIndicator.vo)
    }
    if (pointPrev) {
      pickingIndicator.updateLine(pointPrev.vo)
    }
  }

  setRoutePointHeading() {
    if (!this._selectedIndicator) {
      return
    }
    const tControls = this.world3d.transformRotateControl
    if (tControls.object === this._selectedIndicator) {
      tControls.detach()
      return
    }
    tControls.attach(this._selectedIndicator)
    tControls.setTranslateVisible(false)
  }
  _getRoutePointMesh() {
    const store = this.world3d.store
    let routePointMesh = this._selectedIndicator
    const arr = store.currentRoute.pointMeshes
    if (!routePointMesh && arr.length > 1) {
      routePointMesh = arr[arr.length - 2]
    }
    return routePointMesh
  }
  _modifyPlaneByConstraint(constraint, routePoint, pl, planeMesh) {
    if (constraint === ConstraintEnum.horizontal) {
      pl.normal.copy(unitZ)
      pl.constant = -routePoint.position.z
    } else if (constraint === ConstraintEnum.vertical) {
      // tmp.copy(unitZ).cross(routePoint.normal).normalize()
      tmp.x = -routePoint.normal.y
      tmp.y = routePoint.normal.x
      tmp.z = 0
      tmp.normalize()
      pl.setFromNormalAndCoplanarPoint(tmp, routePoint.position)
    } else {
      // custom orientation
      pl.normal.copy(unitZ).applyQuaternion(planeMesh.quaternion)
      pl.setFromNormalAndCoplanarPoint(pl.normal, routePoint.position)
    }
  }
  /**
   * 设置航点的额外吸附平面
   * @param {string} constraint
   * @see ConstraintEnum
   */
  addAdherePlane(constraint) {
    if (constraint === ConstraintEnum.none) {
      return
    }
    const routePointMesh = this._getRoutePointMesh()
    if (!routePointMesh) {
      return
    }
    const routePoint = routePointMesh.vo
    const plane = new THREE.Plane()
    const planeMesh = new THREE.Mesh(planeGeo, mtlAdhere.clone())
    planeMesh.material.opacity = 0.3
    this._modifyPlaneByConstraint(constraint, routePoint, plane, planeMesh)
    showPlaneMeshShortly(this.world3d.scene, planeMesh, routePoint.position, plane.normal)
    if (constraint === ConstraintEnum.custom) {
      const tControls = this.world3d.transformRotateControl
      tControls.attach(planeMesh)
      tControls.setTranslateVisible(false)
    }
    planeMesh.userData = {plane}
    this.adherePlaneMeshes.push(planeMesh)
    this._refreshAdherePoints(planeMesh)
  }
  _refreshAdherePoints(planeMesh) {
    this.adhereRoutePoints = this.adhereRoutePoints.filter(it => !it.byPlanes.includes(planeMesh))
    this.adherePlaneMeshes.forEach(meshExisted => {
      if (meshExisted === planeMesh) {
        return
      }
      const adherePoint = this._computeAdhereRoutePoint(planeMesh, meshExisted)
      if (adherePoint) {
        adherePoint.byPlanes = [planeMesh, meshExisted]
        this.adhereRoutePoints.push(adherePoint)
      }
    })
  }
  clearAllAdherePlanes() {
    const tControls = this.world3d.transformRotateControl
    if (this.adherePlaneMeshes.includes(tControls.object)) {
      tControls.detach()
    }
    this.adherePlaneMeshes.forEach(it => it.removeFromParent())
    this.adherePlaneMeshes.length = 0
    this.adhereRoutePoints.length = 0
  }
  /**
   * 设置航点的约束平面
   * @param {string} constraint
   * @see ConstraintEnum
   */
  setConstraintPlane(constraint) {
    if (constraint === ConstraintEnum.none) {
      this.constraint = ConstraintEnum.none
      const tControls = this.world3d.transformRotateControl
      const pArr = this.adherePlaneMeshes.concat([planeMeshConstraint])
      if (pArr.includes(tControls.object)) {
        tControls.detach()
      }
      planeMeshConstraint.removeFromParent()
      return
    }
    const routePointMesh = this._getRoutePointMesh()
    if (!routePointMesh) {
      return
    }
    const routePoint = routePointMesh.vo
    this.constraint = constraint
    const pl = new THREE.Plane()
    this._modifyPlaneByConstraint(constraint, routePoint, pl, planeMeshConstraint)
    showPlaneMeshShortly(this.world3d.scene, planeMeshConstraint, routePoint.position, pl.normal)
    planeMeshConstraint.userData = {plane: pl}
    if (constraint === ConstraintEnum.custom) {
      const tControls = this.world3d.transformRotateControl
      planeMeshConstraint.quaternion.setFromUnitVectors(unitZ, pl.normal)
      tControls.attach(planeMeshConstraint)
      tControls.setTranslateVisible(false)
    }
    pl.setFromNormalAndCoplanarPoint(pl.normal, planeMeshConstraint.position)
  }

  _setRaycasterByPlane() {
    const camera = this.world3d.camera
    _raycaster.near = camera.near
    _raycaster.far = camera.far
    const plane0 = planeMeshConstraint.userData.plane
    const intersected = this.world3d.mouseRaycaster.ray.intersectPlane(plane0, _raycaster.ray.direction)
    if (!intersected) {
      return this.world3d.mouseRaycaster
    }
    plane0.projectPoint(camera.position, _raycaster.ray.origin)
    _raycaster.ray.direction.sub(_raycaster.ray.origin).normalize()
    return _raycaster
  }
  _setRaycasterForAdhere(interPoint, adheredPlane) {
    const camera = this.world3d.camera
    _raycaster.near = camera.near
    _raycaster.far = camera.far
    adheredPlane.projectPoint(camera.position, _raycaster.ray.origin)
    adheredPlane.projectPoint(interPoint, _raycaster.ray.direction)
    _raycaster.ray.direction.sub(_raycaster.ray.origin).normalize()
    return _raycaster
  }

  dropdownRoutePoint(event) {
    if (event.button === MouseButton.right && this._movingIndicator) {
      const index = this.world3d.store.routePoints.indexOf(this._movingIndicator)
      if (index >= 0) {
        this.world3d.store.routePoints.splice(index, 1)
      }
      this._movingIndicator.disposeLine()
      this._movingIndicator.removeFromParent()
      this.world3d.store.currentRoute.removePointMesh(this._movingIndicator)
    } else {
      const store = this.world3d.store
      if (store.stateAddingRoutePoint) {
        if (!this._movingIndicator) {
          return
        }
        // 新增的航点 放置好了位置
        const ctx = {
          routePointIndicator: this._movingIndicator,
          arr: store.routePoints,
          sequence: store.currentRoute.pointMeshes,
          route: store.currentRoute,
          routeArr: store.routeList,
          scene: this.world3d.scene,
        }
        this.world3d.editRecord.addAction(UndoType.addRoutePoint, ctx);
      } else {
        if (!this._selectedIndicator) {
          return
        }
        // 移动的航点 放置好了位置
        const ctx = {
          routePointIndicator: this._selectedIndicator,
          oldRoutePointVO: routePointBak.cloneRoutePoint(),
          routeVO: store.currentRoute,
        }
        this.world3d.editRecord.addAction(UndoType.moveRoutePoint, ctx);
      }
    }
    this._movingIndicator = null
  }

  /**
   * 有辅助平面时，优先和辅助平面网格做射线碰撞
   * @param {THREE.Raycaster} raycaster
   * @return {*[]}
   * @private
   */
  _getIntersects(raycaster) {
    // const planeRefs = this.world3d.store.planeRefMeshes
    const planeRefMesh = this.world3d.store.usingPlaneRef
    let tileIntersects = []
    if (planeRefMesh) {
      tileIntersects = raycaster.intersectObject(planeRefMesh, false)
      if (tileIntersects.length) {
        const normal = tileIntersects[0].face.normal
        normal.transformDirection(tileIntersects[0].object.matrixWorld)
        tmp.setFromMatrixColumn(this.world3d.camera.matrix, 2).negate()
        if (normal.dot(tmp) > 0) {
          normal.negate()
        }
      }
    }
    if (!tileIntersects.length) {
      tileIntersects = raycaster.intersectObject(this.world3d.tiles.group, true)
    }
    return tileIntersects
  }
  _unSelect() {
    if (this._selectedIndicator) {
      this._selectedIndicator.toggleSelected(false)
      this._selectedIndicator = null
      this._distanceLabel.visible = false
    }
  }
  _keepFastArrShortly() {
    if (!this.debouncedClearFast) {
      const clearFast = clearFastArr.bind(this)
      this.debouncedClearFast = debounce(clearFast, 3000)
    }
    this.debouncedClearFast()
    if (this.fastArr.length) {
      return
    }
    const arr = this.world3d.store.currentRoute.pointMeshes
    const dist = 0.35
    const latest = arr[arr.length - 2].vo
    const prev = arr[arr.length - 3].vo
    const dir = new THREE.Vector3().copy(latest.position).sub(prev.position).normalize()
    const quat = new THREE.Quaternion().setFromAxisAngle(latest.normal, Math.PI * 0.5)
    const diff = dir.applyQuaternion(quat).multiplyScalar(dist)
    const camera = this.world3d.camera
    _raycaster.near = camera.near
    _raycaster.far = camera.far
    const awayWall = latest.normal.clone().multiplyScalar(20)
    _raycaster.ray.origin.copy(latest.position).add(diff).add(awayWall)
    _raycaster.ray.direction.copy(latest.normal).negate()
    let inters = this._getIntersects(_raycaster)
    const choices = this.fastArr
    if (inters.length) {
      choices.push(inters[0])
    }
    _raycaster.ray.origin.copy(latest.position).sub(diff).add(awayWall)
    inters = this._getIntersects(_raycaster)
    if (inters.length) {
      choices.push(inters[0])
    }
  }
  // 使用快捷键对航点进行快捷定位
  fastLocatePoint() {
    const store = this.world3d.store
    if (!store.currentRoute) {
      return
    }
    const arr = store.currentRoute.pointMeshes
    if (arr.length < 3) {
      return;
    }
    if (!this._movingIndicator) {
      return;
    }
    this._keepFastArrShortly()
    const choices = this.fastArr
    if (!choices.length) {
      return;
    }
    let choice
    if (choices.length === 1) {
      choice = choices[0]
    } else {
      this.fastIndex++
      this.fastIndex %= 2
      choice = choices[this.fastIndex]
    }
    this._movingIndicator.vo.position.copy(choice.point)
    this._movingIndicator.vo.normal.copy(choice.face.normal)
    console.log('syncToVO from fastLocatePoint')
    this._movingIndicator.syncToVO()
    this._movingIndicator.updateLine()
  }
  pointerMove(event) {
    if (event.buttons === MouseButton.right) {
      return
    }
    const world3d = this.world3d
    const store = this.world3d.store
    let arr = []
    if (store.stateAddingRoutePoint && !this._movingIndicator) {
      const vo = new RoutePointVO()
      const mesh = new RoutePointIndicator(vo, world3d.scene)
      world3d.scene.add(mesh)
      store.routePoints.push(mesh)
      if (!store.currentRoute) {
        store.currentRoute = new RouteVO()
        store.currentRoute.routeOrder = store.routeList.length + 1
      }
      store.currentRoute.pointMeshes.push(mesh)
      this._movingIndicator = mesh
      mesh.inheritRouteProperties(store.currentRoute)
      arr = store.currentRoute.pointMeshes
      if (arr.length > 1) {
        const latest = arr[arr.length - 2]
        mesh.createLine(this.world3d.scene, latest.vo)
      }
    }
    if (!this._movingIndicator && !store.stateMovingRoutePoint) {
      return
    }
    const pickingIndicator = this._selectedIndicator
    if (store.stateMovingRoutePoint && !pickingIndicator) {
      return
    }
    arr = store.currentRoute.pointMeshes
    let raycaster
    if (this.constraint === ConstraintEnum.none || arr.length <= 1) {
      raycaster = world3d.mouseRaycaster
    } else {
      raycaster = this._setRaycasterByPlane()
    }
    let tileIntersects = this._getIntersects(raycaster)
    if (tileIntersects.length) {
      const indicator = store.stateMovingRoutePoint ? pickingIndicator : this._movingIndicator
      const vo = indicator.vo
      let interPoint = tileIntersects[0].point
      let adheredPoint = null
      if (this.adhereRoutePoints.length) {
        let dist = Infinity
        let ind = -1
        this.adhereRoutePoints.forEach((vo, index) => {
          const distance = vo.position.distanceTo(interPoint)
          if (distance < dist && distance < distanceAdhere) {
            dist = distance
            ind = index
          }
        })
        if (ind >= 0) {
          adheredPoint = this.adhereRoutePoints[ind]
          vo.copy(adheredPoint)
        }
      }
      let adheredPlane = null
      if (!adheredPoint) {
        if (this.adherePlaneMeshes.length) {
          const planeMeshes = this.adherePlaneMeshes
          let minIndex = -1
          let minDist = Infinity
          planeMeshes.forEach((mesh, index) => {
            const pl = mesh.userData.plane
            const dist = Math.abs(pl.distanceToPoint(interPoint))
            if (dist <= distanceAdhere && dist < minDist) {
              minIndex = index
              minDist = dist
            }
          })
          if (minIndex >= 0) {
            adheredPlane = planeMeshes[minIndex].userData.plane
            raycaster = this._setRaycasterForAdhere(interPoint, adheredPlane)
            tileIntersects = this._getIntersects(raycaster)
            if (!tileIntersects.length) {
              return
            }
            interPoint = tileIntersects[0].point
          }
        }
        vo.position.copy(interPoint)
        vo.normal.copy(tileIntersects[0].face.normal)
        if (arr.length > 1) {
          // 根据强约束平面 进一步调整航点的航向
          if (this.constraint === ConstraintEnum.horizontal) {
            vo.normal.z = 0
            vo.normal.normalize()
          } else if (this.constraint !== ConstraintEnum.none) {
            // 自定义 或 竖直 约束平面
            const plane0 = planeMeshConstraint.userData.plane
            const constant = plane0.constant
            plane0.constant = 0
            plane0.projectPoint(vo.normal, tmp)
            plane0.constant = constant
            vo.normal.copy(tmp).normalize()
          }
          if (adheredPlane) {
            // 根据弱约束平面 进一步调整航点的航向
            const constant = adheredPlane.constant
            adheredPlane.constant = 0
            adheredPlane.projectPoint(vo.normal, tmp)
            adheredPlane.constant = constant
            vo.normal.copy(tmp).normalize()
          }
        }
      }
      // console.log('syncToVO from pointer move')
      indicator.syncToVO()
      if (store.stateMovingRoutePoint) {
        this._distanceLabel.visible = false
        this._updateIndicatorLines(pickingIndicator)
      } else {
        indicator.updateLine()
        this._distanceLabel.visible = arr.length > 1
        if (arr.length > 1) {
          const distance = indicator.initDroneLineTip(this._distanceLabel.position)
          this._distanceLabel.text = distance.toFixed(2) + 'm'
          this._distanceLabel.updateTextAndSize()
        }
      }
    }
  }
  pointerUpMoved(event) {
    // this.dropdownRoutePoint(event)
  }
  pointerUpUnmoved(event) {
    this.dropdownRoutePoint(event)
    const world3d = this.world3d
    const store = this.world3d.store
    if (event.button === MouseButton.right) {
      if (store.stateAddingRoutePoint || store.stateMovingRoutePoint) {
        this._unSelect()
        store.freeState();
        this._distanceLabel.visible = false
      }
      return;
    }
    if (store.stateMovingRoutePoint && this._selectedIndicator) {
      this._unSelect()
      return;
    }
    if (!store.stateFree && !store.stateMovingRoutePoint) {
      return;
    }
    this._unSelect()
    let routePoint
    const raycaster = world3d.mouseRaycaster
    const itemIntersects = raycaster.intersectObjects(store.routePoints, false)
    if (itemIntersects.length) {
      routePoint = itemIntersects[0].object
    } else {
      const meshes = store.routePoints.map(it => it._drone)
      const intersects = raycaster.intersectObjects(meshes, false)
      if (intersects.length) {
        const index = meshes.indexOf(intersects[0].object)
        routePoint = store.routePoints[index]
      }
    }
    if (routePoint) {
      store.unpickRoute()
      // 选中已经存在的航点
      routePoint.toggleSelected(true)
      routePointBak.copy(routePoint.vo)
      this._selectedIndicator = routePoint
      const distance = routePoint.initDroneLineTip(this._distanceLabel.position)
      if (distance && !store.stateMovingRoutePoint) {
        this._distanceLabel.text = distance.toFixed(2) + 'm'
        this._distanceLabel.updateTextAndSize()
        this._distanceLabel.visible = true
      }
      store.setCurrentRoute()
      const evt = World3dEvent.routePointSelected
      evt.value.vo = routePoint.vo
      world3d.scene.dispatchEvent(evt)
    }
    store.routePoints.forEach(it => {
      if (it === routePoint) {
        return
      }
      it.toggleSelected(false)
    })
    if (!store.stateFree || routePoint) {
      return;
    }
    const pts = store.routePoints.filter(it => it._line4Drone)
    const lines = pts.map(it => it._line4Drone)
    const lineIntersects = raycaster.intersectObjects(lines, false)
    if (lineIntersects.length) {
      const index = lines.indexOf(lineIntersects[0].object)
      routePoint = pts[index]
      const routeVO = store.pickRoute(routePoint)
      store.setCurrentRoute(routeVO)
      if (routeVO) {
        const evt = World3dEvent.routeSelected
        evt.value.vo = routeVO
        world3d.scene.dispatchEvent(evt)
      }
    }
  }
}
